/*****************************************************
*
*               COPYRIGHT (c) 2017-2019 Hargic Corporation
*                         All Rights Reserved
*
* The source code contained or described herein and all documents
* related to the source code ("Material") are owned by Hargic
* Corporation or its licensors.  Title to the Material remains
* with Hargic Corporation or its suppliers and licensors.
*
* The Material is protected by worldwide copyright and trade secret
* laws and treaty provisions. No part of the Material may be used,
* copied, reproduced, modified, published, uploaded, posted, transmitted,
* distributed, or disclosed in any way except in accordance with the
* applicable license agreement.
*
* No license under any patent, copyright, trade secret or other
* intellectual property right is granted to or conferred upon you by
* disclosure or delivery of the Materials, either expressly, by
* implication, inducement, estoppel, except in accordance with the
* applicable license agreement.
*
* Unless otherwise agreed by Hargic in writing, you may not remove or
* alter this notice or any other notice embedded in Materials by Hargic
* or Hargic's suppliers or licensors in any way.
*
** Author: shengda.huang
** Date: 2019-03-23
*
*****************************************************/

/***** Include files ********************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/string.h>

/***** Defines **************************************/
#define LED_PIN   7

/***** Define structure *****************************/

/***** Define constant ******************************/

/***** Define global ********************************/
static int majorNumber = 0;

/*Class 名称，对应/sys/class/下的目录名称*/
static const char *CLASS_NAME = "led_control_class";
/*Device 名称，对应/dev下的目录名称*/
static const char *DEVICE_NAME = "led_control_demo";

static int gpio_status;

static char recv_msg[20];

static struct class *led_control_class = NULL;
static struct device *led_control_device = NULL;

/***** Define Prototype of functions ****************/
MODULE_AUTHOR("ted huang <tednick@163.com>");
MODULE_LICENSE("GPL");


/**
 * @brief
 * @param  None
 * @retval None
 */
static void gpio_config(void)
{
    if(!gpio_is_valid(LED_PIN))
    {
        printk(KERN_ALERT "Error wrong gpio number\n");
        return ;
    }
    gpio_request(LED_PIN, "led_ctr");
    gpio_direction_output(LED_PIN, 1);
    gpio_set_value(LED_PIN, 1);
    gpio_status = 1;
}

static void gpio_deconfig(void)
{
    gpio_free(LED_PIN);
}

/*当用户打开这个设备文件时，调用这个函数*/
static int led_control_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "GPIO init \n");
    gpio_config();
    return 0;
}

/*当用户试图从设备空间读取数据时，调用这个函数*/
static ssize_t led_control_read(struct file *file, char *buf, size_t len, loff_t *offset)
{
    int cnt = 0;
    /*将内核空间的数据copy到用户空间*/
    cnt = copy_to_user(buf, &gpio_status, 1);
    printk(KERN_INFO "cnt %d gpio_status %d\n", cnt, gpio_status);
    if(0 == cnt)
    {
        return 0;
    }
    else
    {
        printk(KERN_ALERT "ERROR occur when reading!!\n");
        return -EFAULT;
    }
    return 1;
}

/*当用户往设备文件写数据时，调用这个函数*/
static ssize_t led_control_write(struct file *file, const char *buf, size_t len, loff_t *offset)
{
    /*将用户空间的数据copy到内核空间*/
    int cnt = copy_from_user(recv_msg, buf, len);
    printk(KERN_INFO "cnt %d recv_msg %.*s\n", cnt, len, recv_msg);
    if(0 == cnt)
    {
        if(0 == memcmp(recv_msg, "on", 2))
        {
            printk(KERN_INFO "LED ON!\n");
            gpio_set_value(LED_PIN, 1);
            gpio_status = 1;
        }
        else
        {
            printk(KERN_INFO "LED OFF!\n");
            gpio_set_value(LED_PIN, 0);
            gpio_status = 0;
        }
    }
    else
    {
        printk(KERN_ALERT "ERROR occur when writing!!\n");
        return -EFAULT;
    }
    return len;
}

static long led_control_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch (cmd)
    {
        case 1:
            printk(KERN_INFO "LED ON!\n");
            gpio_set_value(LED_PIN, 1);
            gpio_status = 1;
            break;
        case 0:
            printk(KERN_INFO "LED OFF!\n");
            gpio_set_value(LED_PIN, 0);
            gpio_status = 0;
            break;
        default:
            return -ENOTTY;
    }

    return 0;
}

/*当用户打开设备文件时，调用这个函数*/
static int led_control_release(struct inode *node,struct file *file)
{
    printk(KERN_INFO "Release!!\n");
    gpio_deconfig();
    return 0;
}

/*File opertion 结构体，我们通过这个结构体建立应用程序到内核之间操作的映射*/
static struct file_operations file_oprts =
{
	.owner = THIS_MODULE,
    .open = led_control_open,
    .release = led_control_release,
    .read = led_control_read,
    .write = led_control_write,
    .unlocked_ioctl = led_control_ioctl,
};


static int __init led_control_init(void)
{
    printk(KERN_INFO "Driver init\n");

    /*注册一个新的字符设备，返回主设备号*/
    majorNumber = register_chrdev(0, DEVICE_NAME, &file_oprts);
    if(majorNumber < 0 )
    {
        printk(KERN_ALERT "Register failed!!\n");
        return majorNumber;
    }
    printk(KERN_INFO "Registe success, major number is %d\n", majorNumber);

    /*以CLASS_NAME创建一个class结构，这个动作将会在/sys/class目录创建一个名为CLASS_NAME的目录*/
    led_control_class = class_create(THIS_MODULE, CLASS_NAME);
    if(IS_ERR(led_control_class))
    {
        unregister_chrdev(majorNumber, DEVICE_NAME);
        return PTR_ERR(led_control_class);
    }

    /*以DEVICE_NAME为名，参考/sys/class/CLASS_NAME在/dev目录下创建一个设备：/dev/DEVICE_NAME*/
    led_control_device = device_create(led_control_class, NULL, MKDEV(majorNumber,0) , NULL, DEVICE_NAME);
    if(IS_ERR(led_control_device))
    {
        class_destroy(led_control_class);
        unregister_chrdev(majorNumber, DEVICE_NAME);
        return PTR_ERR(led_control_device);
    }
    printk(KERN_INFO "led_control device init success!!\n");
    return 0;
}

/*销毁注册的所有资源，卸载模块，这是保持linux内核稳定的重要一步*/
static void __exit led_control_exit(void)
{
    device_destroy(led_control_class, MKDEV(majorNumber,0));
    class_unregister(led_control_class);
    class_destroy(led_control_class);
    unregister_chrdev(majorNumber, DEVICE_NAME);
    printk(KERN_INFO "led_control device remove success!!\n");
}

module_init(led_control_init);
module_exit(led_control_exit);

/* End of file */



